package com.ease.util {

   import com.ease.util.logging.LogManager;
   import com.ease.util.reflection.*;

   import flash.system.ApplicationDomain;
   import flash.utils.Dictionary;
   import flash.utils.getDefinitionByName;
   import flash.utils.getQualifiedClassName;
   import flash.utils.getQualifiedSuperclassName;

   import mx.collections.ArrayCollection;
   import mx.core.IFlexModuleFactory;
   import mx.logging.ILogger;
   import mx.modules.ModuleManager;
   import mx.skins.ProgrammaticSkin;
   import mx.utils.StringUtil;

   import spark.skins.SparkSkin;

   /**
    * @private
    *
    * In all the methods in this utility class that expect an object parameter to identify
    * the type of interest, either an instance of that type, or the associated Class object
    * can be used as the said parameter to the method.
    *
    * <p>The OPTION constants in this class define option flags that can be combined using
    * the bitwise and/or operations and used as parameters to the applicable methods (to
    * specify some variation of the reflection operation in question).
    */
   public class ReflectionUtil
   {
      private static const _logger:ILogger = LogManager.getLogger(ReflectionUtil);

      /**
       * This option constant to indicate default behavior to a util method in this class.
       */
      public static const OPTION_NONE:uint = 0;

      /**
       * This option constant indicates that only application-defined (as opposed to flex
       * framework defined) classes should be considered by the requested reflection lookup.
       */
      public static const OPTION_APP_DEFINED_TYPES_ONLY:uint = 1 << 0;

      /**
       * This option constant indicates that a given type can be ignored if its definition
       * can't be found by the requested reflection lookup.
       */
      public static const OPTION_IGNORE_TYPES_NOT_FOUND:uint = 1 << 1;

      /**
       * Represents the 'readonly' access modifier (e.g., for a property definition).
       */
      public static const ACCESS_READONLY:String = "readonly";

      /**
       * Represents the 'writeonly' access modifier (e.g., for a property definition).
       */
      public static const ACCESS_WRITEONLY:String = "writeonly";

      /**
       * Represents the 'readwrite' access modifier (e.g., for a property definition).
       */
      public static const ACCESS_READWRITE:String = "readwrite";

      /**
       * Represents any access modifier. Note that this special kind is not available
       * in the type description returned by Flex, but is used as convenience constant
       * in some of the methods provided by this class.
       */
      public static const ACCESS_ANY:String = "any";

      /**
       * A string constant used to reference the [ProcessingOrder] tag in
       * [ProcessingOrder(tag="...", order="...")] metadata.
       */
      private static const PROCESSING_ORDER_TAG:String = "ProcessingOrder";

      /**
       * A string constant used to reference the "tag" argument in the
       * [ProcessingOrder(tag="...", order="...")] metadata.
       */
      private static const TAG_KEY:String = "tag";

      /**
       * A string constant used to reference the "order" argument in the
       * [ProcessingOrder(tag="...", order="...")] metadata.
       */
      private static const ORDER_KEY:String = "order";

      [ArrayElementType("String")]
      /**
       * Finds the metatag definitions by the given name for the specified object/type (and
       * all its base classes) and returns the values of the specified parameter in all
       * matching tag definitions. <code>Object</code> and most other flex library classes
       * are not included in the metadata search.
       */
      public static function getClassMetadata( object:Object,
                                               metatag:String,
                                               key:String ):Array/*String*/ {
         var allClassMetadata:Array = getUninheritedClassMetadata( object, metatag, key );
         var baseClasses:Array/*Class*/ =
            getBaseClasses( object, OPTION_APP_DEFINED_TYPES_ONLY );
         for each (var type:Class in baseClasses) {
            var classMetadata:Array/*String*/ =
               getUninheritedClassMetadata( type, metatag, key );
            allClassMetadata = allClassMetadata.concat( classMetadata );
         }
         return allClassMetadata;
      }

      /**
       * Finds the class-level metatag definition for the given object and tag name, and
       * returns the value of the tag paramter the given key. If no key is specified,
       * returns value of any parameter.
       */
      public static function getClassTagValue( object:Object, metatag:String,
                                               key:String = null ):String {
         var result:Array = getClassTagValues( object, metatag, key );
         if (result == null) {
            return null;
         }
         return result[0];
      }

      /**
       * Finds the class-level metatag definitions for the given object and tag name, and
       * returns an array containing values of the tag paramter the given key. If no key
       * is specified, returns values of any parameter.
       */
      [ArrayElementType("String")]
      public static function getClassTagValues( object:Object, metatag:String,
                                                key:String = null ):Array {
         var result:Array = getUninheritedClassMetadata(object, metatag, key);
         return result;
      }

      /**
       * Finds the class-level metatag definition for the given object and tag name, such
       * that the tag has the given searchValue for the parameter with the given searchKey.
       * It then returns the value of the tag paramter with the given valueKey. Metadata is
       * looked up for the concrete class of the object as well as all the classes it
       * extends.
       *
       * <p>If searchValue is null or undefined, looks for a tag that doesn't specify any
       * params with the given searchKey.
       */
      public static function getFilteredClassTagValue( object:Object, metatag:String,
                                                       searchKey:String, searchValue:String,
                                                       valueKey:String  ):String {
         var type:Class = object.constructor;
         while (type != null) {
            var value:String = getUninheritedFilteredClassTagValue(
               object, metatag, searchKey, searchValue, valueKey );
            if (value != null) {
               return value;
            }
            type = getBaseClass( type, object, OPTION_APP_DEFINED_TYPES_ONLY );
         }
         return null;
      }

      /**
       * Returns a map of key-values for the args to the metadata tag that satisfies the
       * given criteria. The criteria consist of the given name for the tag and a key-value
       * pair as an arg to the tag.
       */
      public static function getClassTagData( object:*, metatag:String, key:String = null,
                                              value:String = null ):Dictionary {
         var tags:Vector.<TagReflectionInfo> =
            ReflectionManager.instance.getClassTagInfos(object, metatag);

         var keyValues:Dictionary = new Dictionary();
         if (tags.length == 0) {
            return keyValues;
         }
         var tag:TagReflectionInfo;
         if (key && value && tags.length > 1) {
            tag = tagWithArgKeyAndValue( tags, key, value, object );
         }
         if (! tag) {
            tag = tags[0];
         }
         var args:Vector.<TagArgReflectionInfo> = tag.argInfos;
         for each (var arg:TagArgReflectionInfo in args) {
            var argKey:String = arg.key;
            var argValue:String = arg.value;
            argValue = resolveTagValue( argValue, object ) as String
            keyValues[argKey] = argValue;
         }
         return keyValues;
      }

      /**
       * Finds the method-level metatag definition for the given object, method and tag name,
       * such that the tag has the given searchValue for the parameter with the given
       * searchKey. It then returns the value of the tag paramter with the given valueKey.
       */
      public static function
         getFilteredMethodTagValue( object:Object, methodName:String, metatag:String,
                                    searchKey:String, searchValue:String,
                                    valueKey:String  ):String {
         var methodInfo:MethodReflectionInfo =
            ReflectionManager.instance.getTaggedMethodInfo(object, methodName);
         if (methodInfo == null) {
            return null;
         }
         var tags:Vector.<TagReflectionInfo> = methodInfo.getTagInfosByName(metatag);
         var tagValue:String =
            getParamValueFromValueFilteredTag( object, tags, searchKey, searchValue,
               valueKey );
         return tagValue;
      }

      [ArrayElementType("String")]
      public static function getMethodTagValues( object:Object, metatag:String,
                                                 key:String = null ) : Array {
         var tagVals:Array = new Array();
         if (! object) {
            return tagVals;
         }
         var target:Object = object;
         var methods:Vector.<MethodReflectionInfo> =
            ReflectionManager.instance.getTaggedMethodInfos(object, metatag);
         for each (var method:MethodReflectionInfo in methods) {
            var vals:Vector.<String> = method.getTagArgValues(metatag);
            for each (var tagValue:String in vals) {
               tagValue = resolveTagValue( tagValue, object ) as String;
               tagVals.push( tagValue );
            }
         }
         return tagVals;
      }

      [ArrayElementType("String")]
      /**
       * Finds all the types (i.e., the class, the super classes and the interfaces) of the
       * given object.
       *
       * @param object
       *    The object whose type list is to determined.
       *
       * @return
       *    Returns the type names.
       */
      public static function getTypes( object:Object ):Array/*String*/ {
         var types:Array = new Array();
         if (object == null) {
            return types;
         }
         var typeName:String = getQualifiedClassName( object );
         types.push( typeName );
         var classDescription:ClassReflectionInfo =
            ReflectionManager.instance.getClassInfo( object );
         var baseClasses:Vector.<String> = classDescription.baseClassNames;
         for each (var superClassName:String in baseClasses) {
            types.push( String( superClassName ) );
         }
         var interfaces:Vector.<String> = classDescription.interfaceNames;
         for each (var interfaceName:String in interfaces) {
            types.push( String( interfaceName ) );
         }
         return types;
      }

      [ArrayElementType("Class")]
      /**
       * Returns Class objects for all the classes extended by the class of the given object.
       */
      public static function getBaseClasses( object:Object,
                                             options:uint = OPTION_NONE ):Array/*Class*/ {
         var lookupAppDefinedClassesOnly:Boolean =
            (options & OPTION_APP_DEFINED_TYPES_ONLY) != 0;
         var types:Array/*Class*/ = [];
         var classDescription:ClassReflectionInfo =
            ReflectionManager.instance.getClassInfo( object );
         var baseClasses:Vector.<String> = classDescription.baseClassNames;
         for each (var className:String in baseClasses) {
            if (lookupAppDefinedClassesOnly && isFlexTypeName( className )) {
               continue;
            }
            var type:Class = getDefinition( className, object );
            types.push( type );
         }
         return types;
      }

      /**
       * Returns Class object representing the given object's base class. The arg can either
       * be an instance or a Class.
       */
      public static function getBaseClass( type:Class, moduleReference:* = null,
                                           options:uint = OPTION_NONE ):Class {
         var lookupAppDefinedClassesOnly:Boolean =
            (options & OPTION_APP_DEFINED_TYPES_ONLY) != 0;
         var baseClassName:String = getQualifiedSuperclassName( type );
         if (lookupAppDefinedClassesOnly && isFlexTypeName( baseClassName )) {
            return null;
         }
         var baseClass:Class = getDefinition( baseClassName, moduleReference );
         return baseClass;
      }

      /**
       * Looks up and returns types of the parameters to the method of the given name
       * defined on the given object.
       */
      public static function getArgTypes( declaringType:Class, moduleReference:*,
                                          methodName:String ):Array/*Class*/ {
         var types:Array/*Class*/ = [];
         var method:MethodReflectionInfo = getMethodDescription( declaringType, methodName );
         if (method == null) {
            return types;
         }
         var params:Vector.<String> = method.argTypeNames;
         for each (var typeName:String in params) {
            var type:Class = getDefinition( typeName, moduleReference );
            types.push( type );
         }
         return types;
      }

      /**
       * Checks whether the given object has a method by the given name. The object can be
       * an instance or the Class object for the type in question.
       * Note that only tagged methods are considered.
       */
      public static function hasMethod( object:Object, methodName:String ):Boolean {
         // See if we can avoid calling describeType.
         if (object.hasOwnProperty( methodName )) {
            return true;
         }
         // The method may be on the class; use reflection to figure out.
         var method:MethodReflectionInfo = getMethodDescription( object, methodName );
         return method != null;
      }

      /**
       * Indicates whether the given object type is annotated with the given metadata tag.
       */
      public static function hasClassMetadata( object:Object,
                                               metatag:String ):Boolean {
         var hasTag:Boolean = hasUninheritedClassMetadata( object, metatag );
         if (hasTag) {
            return true;
         }
         var baseClasses:Array/*Class*/ =
            getBaseClasses( object, OPTION_APP_DEFINED_TYPES_ONLY );
         for each (var type:Class in baseClasses) {
            hasTag = hasUninheritedClassMetadata( type, metatag );
            if (hasTag) {
               return true;
            }
         }
         return false;
      }

      /**
       * Returns one TaggedPropertyInfo object per property-tagValue pair, where properties
       * are those that are tagged with the given metatag, and tagValue is a parameter to the
       * tag. For example, the following would result in a result array of length 3:
       *
       * <p><code>[EventHandler("event1", foo="event2", event3")]</code>
       *
       * <p>If the key arg is specified, it also searches for all the tag params that have
       * the given key (that is, in addition to the empty key).
       */
      [ArrayElementType("com.ease.util.TaggedPropertyInfo")]
      public static function getTaggedProperties(
         object:Object, metatag:String, key:String = null ):Array/*TaggedPropertyInfo*/ {
         var props:Vector.<PropertyReflectionInfo> =
            getTaggedPropertyDescriptions( object, metatag );
         var values:Array/*TaggedPropertyInfo*/ = [];
         for each (var prop:PropertyReflectionInfo in props) {
            var tags:Vector.<String> = prop.getTagArgValues(metatag, key);
            var pi:TaggedPropertyInfo;
            if (tags.length == 0) {
               pi = newPropertyInfo( prop, object );
               values.push( pi );
               continue;
            }
            for each (var tag:String in tags) {
               pi = newPropertyInfo( prop, object );
               pi.tagValue = resolveTagValue( tag, object ) as String;
               values.push( pi );
            }
         }
         return values;
      }

      [ArrayElementType("String")]
      /**
       * Returns the names of the given object's public properties.
       * Note that the "accessors" (get/set) are also returned in the result.
       *
       * @param accessorsMode
       *    The access mode for the accessors. Can be one of the following
       *    string values: "readwrite", "readonly", "writeonly", "any".
       *    The last one means "regardless of the mode". This parameter is taken
       *    into consideration only for accessors, because regular public
       *    properties do not have access mode.
       *
       * @param sortTag
       *    In some cases many of the properties of a class may be marked with
       *    [ProcessingOrder(tag="sortTag", order="...")] meta tags.
       *    If specified the <code>sortTag</code> will force this method to respect
       *    the ordering between the properties marked with
       *    [ProcessingOrder(tag="sortTag", order="...")]. i.e: it will return
       *    property name for property marked with
       *    [ProcessingOrder(tag="sortTag", order="1")] before
       *    property name for property marked with
       *    [ProcessingOrder(tag="sortTag", order="2")].
       *    Important to keep is mind is that <code>sortTag</code> is not
       *    acting as a filter.
       *
       * @see ACCESS_READONLY, ACCESS_WRITEONLY, ACCESS_READWRITE, ACCESS_ANY
       */
      public static function getPropertyNames(object:Object,
                                              accessorsMode:String = ACCESS_ANY,
                                              sortTag:String = null):Array {
         var result:Array = [];
         var props:Vector.<PropertyReflectionInfo> =
            getPropertyDescriptions(object, accessorsMode, sortTag);
         for each (var prop:PropertyReflectionInfo in props) {
            var propName:String = prop.name;
            result.push(propName);
         }
         return result;
      }

      [ArrayElementType("String")]
      /**
       * Returns the names of the object's public properties which has a specific tag.
       * Note that the "accessors" (get/set) are also returned in the result.
       *
       * @param metatag
       *    The tag that a property should have in its metadata.
       *
       * @param sortTag
       *    In some cases many of the properties of a class may be marked with
       *    [ProcessingOrder(tag="sortTag", order="...")] meta tags.
       *    If specified the <code>sortTag</code> will force this method to respect
       *    the ordering between the properties marked with
       *    [ProcessingOrder(tag="sortTag", order="...")]. i.e: it will return
       *    property name for property marked with
       *    [ProcessingOrder(tag="sortTag", order="1")] before
       *    property name for property marked with
       *    [ProcessingOrder(tag="sortTag", order="2")].
       *    Important to keep is mind is that <code>sortTag</code> is not
       *    acting as a filter.
       */
      public static function getTaggedPropertyNames(object:Object,
                                                    metatag:String,
                                                    sortTag:String = null):Array {
         var result:Array = [];
         var props:Vector.<PropertyReflectionInfo> =
            getTaggedPropertyDescriptions(object, metatag, sortTag);
         for each (var prop:PropertyReflectionInfo in props) {
            var propName:String = prop.name;
            result.push(propName);
         }
         return result;
      }

      /**
       * Returns property info for all the properties of the given object that are tagged
       * with the given metatag. The returned info objects contain all key-value pairs for
       * the parameters supplied to the tags.
       */
      [ArrayElementType("com.ease.util.TaggedPropertyInfo")]
      public static function getAllKeyValuesForTaggedProperties( object:Object,
                                                                 metatag:String ):Array {
         var props:Vector.<PropertyReflectionInfo> =
            getTaggedPropertyDescriptions( object, metatag );
         var values:Array = [];
         for each (var prop:PropertyReflectionInfo in props) {
            var tagArgs:Vector.<TagArgReflectionInfo> = prop.getTagArgInfos(metatag);
            var pi:TaggedPropertyInfo = newPropertyInfo( prop, object );
            pi.tagArgs = tagArgs;
            values.push( pi );
         }
         return values;
      }

      /**
       * Returns info for the property tagged with the given metatag on the given object.
       * A 'property' is a public member variable or accessor. The given metatag can be of
       * the form "a|b|...|z", in which case, each individual tag is tried in order until a
       * property with that tag is found.
       *
       * <p>The returned property info contains the value, if any, supplied to the tag as a
       * parameter.
       */
      public static function
         getSingleTaggedProperty( object:Object, metatag:String ):TaggedPropertyInfo {
         var tags:Array/*String*/ = metatag.split( "|" );
         var pi:TaggedPropertyInfo;
         for each (var tag:String in tags) {
            pi = getSingleTaggedProperty_SimpleTag( object, tag );
            if (pi != null) {
               return pi;
            }
         }
         return pi;
      }

      private static function
         getSingleTaggedVariable( object:Object, metatag:String ) : TaggedPropertyInfo {
         var variables:Vector.<PropertyReflectionInfo> =
            getTaggedVariableDescriptions( object, metatag );
         if (variables.length == 0) {
            return null;
         }
         var variable:PropertyReflectionInfo = variables[0];
         var pi:TaggedPropertyInfo = newPropertyInfo( variable, object );
         var tags:Vector.<TagArgReflectionInfo> = variable.getTagArgInfos(metatag);
         if (tags.length > 0) {
            pi.tagValue = tags[0].value;
         }
         return pi;
      }

      private static function
         getSingleTaggedAccessor( object:Object, metatag:String ) : TaggedPropertyInfo {
         var members:Vector.<PropertyReflectionInfo> =
            getTaggedAccessorDescriptions( object, metatag );
         if (members.length == 0) {
            return null;
         }
         var member:PropertyReflectionInfo = members[0];
         var pi:TaggedPropertyInfo = newPropertyInfo( member, object );
         var tags:Vector.<TagArgReflectionInfo> = member.getTagArgInfos(metatag);
         if (tags.length > 0) {
            pi.tagValue = tags[0].value;
         }
         return pi;
      }

      /**
       * Returns one TaggedMethodInfo object per method-tagValue pair, where methods are
       * those that are tagged with the given metatag, and tagValue is a parameter to the
       * tag. For example, the following would result in a result array of length 3:
       *
       * <p><code>[EventHandler("event1", "event2", event3")]</code>
       *
       * <p>If the key arg is specified, it also searches for all the tag params that have
       * the given key (that is, in addition to the empty key).
       */
      [ArrayElementType("com.ease.util.TaggedMethodInfo")]
      public static function getTaggedMethods( object:Object, metatag:String,
                                               key:String = null ) : Array {
         var methods:Vector.<MethodReflectionInfo> =
            getTaggedMethodDescriptions( object, metatag );
         var values:Array = new Array();
         for each (var method:MethodReflectionInfo in methods) {
            var tags:Vector.<String> = method.getTagArgValues(metatag, key);
            var mi:TaggedMethodInfo;
            if (tags.length == 0) {
               mi = newMethodInfo( method, object );
               values.push( mi );
               continue;
            }
            for each (var tag:String in tags) {
               mi = newMethodInfo( method, object );
               mi.tagValue = resolveTagValue( tag, object ) as String;
               values.push( mi );
            }
         }
         return values;
      }

      /**
       * Returns method info for all the methods defined on the given object that are tagged
       * with the given metatag. The returned info objects contain all key-value pairs for
       * the parameters supplied to the tags.
       */
      [ArrayElementType("com.ease.util.TaggedMethodInfo")]
      public static function getAllKeyValuesForTaggedMethods( object:Object,
                                                              metatag:String ):Array {
         var methods:Vector.<MethodReflectionInfo> =
            getTaggedMethodDescriptions( object, metatag );
         var values:Array = new Array();
         for each (var method:MethodReflectionInfo in methods) {
            var mi:TaggedMethodInfo = newMethodInfo( method, object );
            var tagArgs:Vector.<TagArgReflectionInfo> = method.getTagArgInfos(metatag);
            mi.tagArgs = tagArgs;
            values.push( mi );
         }
         return values;
      }

      /**
       * Returns the name of a single method tagged with the given metadata tag.
       */
      public static function getSingleTaggedMethod( object:Object, metatag:String,
                                                    value:String = null ):String {
         var methods:Vector.<MethodReflectionInfo> =
            getTaggedMethodDescriptions( object, metatag, value );
         if (methods.length == 0) {
            return null;
         }
         return methods[0].name;
      }

      /**
       * Returns the value of the given key in the collection of args to some metatag or
       * annotation.
       */
      public static function getKeyedValueFromTagParams( args:Vector.<TagArgReflectionInfo>,
                                                         key:String ):String {
         for each (var argInfo:TagArgReflectionInfo in args) {
            if (argInfo.key == key) {
               return argInfo.value;
            }
         }
         return null;
      }

      /**
       * Given a set of metatag definitions for some class element of the given object,
       * finds the tag that has the given value for the specified key.
       *
       * <p>If searchValue is null or undefined, looks for a tag that doesn't specify any
       * params with the given key.
       */
      public static function
         getValueFilteredTag( object:Object, tags:Vector.<TagReflectionInfo>,
                              searchKey:String, searchValue:String ):TagReflectionInfo {
         for each (var tag:TagReflectionInfo in tags) {
            var arg:TagArgReflectionInfo = tag.getArgInfoByKey(searchKey);
            if (arg == null) {
               // tag contains no params with the given key
               if (searchValue == null) {
                  // Null searchValue means look for the tag that contains no params with
                  // the given key.
                  return tag;
               }
               continue;
            }
            var argValue:String = arg.value;
            argValue = resolveTagValue( argValue, object ) as String;
            if (argValue == searchValue) {
               return tag;
            }
         }
         return null;
      }

      /**
       * Given a set of metatag definitions in the form of xml for some class element of the
       * given object, finds the tag that has the given value for the specified search-key,
       * and returns the value for the given value-key.
       *
       * <p>If searchValue is null or undefined, looks for a tag that doesn't specify any
       * params with the given searchKey.
       */
      private static function
         getParamValueFromValueFilteredTag( object:Object, tags:Vector.<TagReflectionInfo>,
                                            searchKey:String, searchValue:String,
                                            valueKey:String ):String {
         var tag:TagReflectionInfo = getValueFilteredTag( object, tags, searchKey,
            searchValue );
         if (tag == null) {
            return null;
         }
         var argInfo:TagArgReflectionInfo = tag.getArgInfoByKey(valueKey);
         if (argInfo == null) {
            return null;
         }
         var argValue:String = argInfo.value;
         argValue = resolveTagValue( argValue, object ) as String;
         return argValue;
      }

      [ArrayElementType("com.ease.util.TaggedPropertyInfo")]
      /**
       * Returns property info for those properties of the given object that are tagged with
       * the given metatag, such that the tag has a certain value for the specified
       * parameter. If a resultKey is also provided, the tagValue in the returned property
       * info is the value of the tag parameter by this name.
       */
      public static function
         getFilteredTaggedProperties( object:Object, metatag:String, searchKey:String,
                                      searchValue:String,
                                      resultKey:String = null ):Array/*TaggedPropertyInfo*/ {
         var props:Vector.<PropertyReflectionInfo> =
            getTaggedPropertyDescriptions( object, metatag );
         var filteredProps:Array = [];
         for each (var prop:PropertyReflectionInfo in props) {
            var tags:Vector.<TagReflectionInfo> = prop.getTagInfosByName(metatag);
            var tag:TagReflectionInfo = getValueFilteredTag( object, tags, searchKey,
               searchValue );
            if (tag == null) {
               continue;
            }
            var pi:TaggedPropertyInfo = newPropertyInfo( prop, object );
            filteredProps.push( pi );
            if (resultKey != null) {
               pi.tagValue = getTagParamValue( object, tag, resultKey );
            }
         }
         return filteredProps;
      }

      public static function isValueType( type:Class ):Boolean {
         switch (type) {
            case String:
            case int:
            case Number:
            case Date:
            case uint:
            case Boolean:
               return true;
         }
         return false;
      }

      public static function isSimpleType( type:String ):Boolean {
         switch (type) {
            case "Object":
            case "String":
            case "Boolean":
            case "Number":
            case "Array":
            case "Date":
            case "int":
            case "uint":
            case "void":
            case "null":
            case "undefined":
               return true;
         }
         return false;
      }

      /**
       * Constructs an instance of the given type using an optional module reference.
       *
       * @param typeName
       *    Name of the type whose instance is desired
       *
       * @param moduleReference
       *    An object of any type from the same module as the given type name
       *
       * @param constructorArg
       *    Argument for the constructor; null implies no-arg construction
       *
       * @return
       *    A new instance of the given type if successful, otherwise null
       */
      public static function newInstance( typeName:String,
                                          moduleReference:* = null,
                                          constructorArg:Object = null ):Object {
         var factory:ObjectFactory = ReflectionUtil.newFactory( typeName, moduleReference );
         if (! factory) {
            return null;
         }
         return factory.newInstance( constructorArg );
      }

      /**
       * Creates a shallow clone of the given object, with an optional constructor arg. The
       * clone contains the same public property values that the given object does. These
       * properties include both variables and accessors. If constructorArg is null, the
       * constructor is assumed to take no args.
       */
      public static function
         createShallowClone( object:Object, constructorArg:Object = null ):* {
         if (isOfUncloneableType(object)) {
            return object;
         }
         var type:Class = Object( object ).constructor;
         // Special handling for Array
         if (type == Array) {
            // Array.concat() should return a new Array with the objects from the
            // original array copied over thus creating a shallow clone.
            return (object as Array).concat();
         }
         var clone:Object;
         if (constructorArg == null) {
            clone = new type();
         } else {
            clone = new type( constructorArg );
         }
         copyPropertyValues( object, clone );
         return clone;
      }

      /**
       * Sets properties on the second arg with the values found on the first arg, for the
       * properties that are defined on the static type of the <strong>first</strong>
       * or <code>second</code> arg.
       *
       * @param useSourceType
       * @default true
       *    If set to <code>true</code> the <code>source</code> static type is used
       *    to retrieve the list of properties to copy. If set to <code>false</code>,
       *    then the target static type is used to retrieve the list of properties.
       *
       * @return
       *    The <code>target</code> instance.
       *
       * The returned value allows for shorter one liner constructions:
       * <listing version="3.0">
       * var copy:Object = ReflectionUtil.copyPropertyValues(source, new CustomObjectType());
       * </listing>
       */
      public static function copyPropertyValues( source:Object, target:Object,
                                                 useSourceType:Boolean = true ):Object {
         if (source == null || target == null) {
            return target;
         }

         var props:Vector.<PropertyReflectionInfo> = useSourceType ?
            getPropertyDescriptions( source, ACCESS_READWRITE ):
            getPropertyDescriptions( target, ACCESS_READWRITE );
         for each (var prop:PropertyReflectionInfo in props) {
            var propName:String = prop.name;
            target[propName] = source[propName];
         }

         return target;
      }

      /**
       * Creates a deep clone of the given object. Optionally a property modifier
       * can be specified to allow the property values of the clone to be modified.
       * Assumes that the constructor takes no args.
       *
       * @param object
       *    Object to be cloned.
       *
       * @param propertyModifier
       *    Callback to change the value of a property of the supplied object. It
       * is invoked for all properties and nested properties starting with the
       * nested properties. If the callback returns an unassignable object type then
       * the modification will not be applied.
       * <p>Signature:
       *    function(propertyProvider:Object, propName:String, property:Object):Object
       *
       * @return
       *    Cloned object.
       */
      public static function createDeepClone(
         object:Object,
         propertyModifier:Function = null ):Object {
         if (isOfUncloneableType(object)) {
            return object;
         }
         var className:String = flash.utils.getQualifiedClassName(object);
         var type:Class = ReflectionUtil.getDefinition(className, object);
         if (isValueType( type )) {
            return object;
         }
         // Special handling for Array
         if (type == Array) {
            return deepCloneArray( object as Array, propertyModifier );
         }
         var clone:Object = new type();
         var props:Vector.<PropertyReflectionInfo> =
            getPropertyDescriptions( type, ACCESS_READWRITE );
         for each (var prop:PropertyReflectionInfo in props) {
            var propName:String = prop.name;
            var originalPropValue:Object = object[propName];
            var propValue:Object = createDeepClone(originalPropValue, propertyModifier);
            if (propertyModifier != null) {
               var modifiedValue:Object = propertyModifier(object, propName, propValue);
               if (isOfType(modifiedValue, prop.typeName)) {
                  propValue = modifiedValue;
               } else {
                  var msg:String = StringUtil.substitute(
                     "Expected an object of type {0} but received object was {1}",
                     prop.typeName, modifiedValue);
                  Diagnostics.assert(false, msg);
               }
            }
            clone[propName] = propValue;
         }
         return clone;
      }

      /**
       * Returns true if the given object cannot be cloned.
       */
      public static function isOfUncloneableType(object:Object):Boolean {
         return object == null || object is Class;
      }

      /**
       * Will return a deep clone of the supplied array.
       */
      private static function deepCloneArray(
         originalArray:Array,
         propertyModifier:Function = null ):Array {
         if (originalArray == null) {
            return null;
         }
         var clonedArray:Array = [];
         for each (var object:Object in originalArray) {
            var clonedObject:Object = createDeepClone( object, propertyModifier );
            clonedArray.push( clonedObject );
         }
         return clonedArray;
      }

      /**
       * Constructs a factory for the given type name using an optional module reference.
       *
       * @param typeName
       *  Name of the type whose factory is desired
       *
       * @param moduleReference
       *  An object of any type from the same module as the given type name
       *
       * @return
       *  An instance of the factory
       */
      public static function newFactory( typeName:String, moduleReference:* ):ObjectFactory {
         if (typeName == null) {
            return null;
         }
         var error:Error;
         try {
            // The type may be coming from some dynamically loaded module, so it may not be
            // found in the current app domain; hence try-catch.
            var classDefn:Class = getDefinitionByName( typeName ) as Class;
            return ObjectFactory.newFactoryByClass( classDefn );
         } catch (err:Error) {
            // save for later
            error = err;
         }
         var factory:IFlexModuleFactory =
            ModuleManager.getAssociatedFactory( moduleReference );
         if (factory == null) {
            throw error;
         }
         return ObjectFactory.newFactoryByTypeName( typeName, factory );
      }

      /**
       * Finds the class definition for the given type name using an optional module
       * reference.
       *
       * @param typeName
       *    Name of the type whose definition is desired.
       *
       * @param moduleReference
       *    An object of any type from the same module as the given type name.
       *
       * @param options
       *    These option bits can be used to affect the lookup behavior. E.g., specifying
       *    OPTION_IGNORE_TYPES_NOT_FOUND means that the method will not assert or throw if
       *    the requested type cannot be found.
       *
       * @return
       *    An instance of Class.
       */
      public static function getDefinition( typeName:String,
                                            moduleReference:* = null,
                                            options:uint = OPTION_NONE ):Class {
         if (typeName == null) {
            return null;
         }
         var classDefn:Class;
         var error:Error;
         try {
            // The type may be coming from some dynamically loaded module, so it may not be
            // found in the current app domain; hence try-catch.
            classDefn = getDefinitionByName( typeName ) as Class;
            return classDefn;
         } catch (err:Error) {
            // save for later
            error = err;
         }
         var appDomain:ApplicationDomain;
         if (moduleReference != null) {
            var referenceClass:Class = moduleReference as Class;
            if (referenceClass != null) {
               // Instantiates the class because getAssociatedFactory cannot
               // be called with a Class object.
               moduleReference = new referenceClass();
            }
            var factory:IFlexModuleFactory =
               ModuleManager.getAssociatedFactory( moduleReference );
            if (factory != null) {
               appDomain = factory.info().currentDomain;
            }
         }

         if (appDomain == null) {
            if ((options & OPTION_IGNORE_TYPES_NOT_FOUND) != 0) {
               return null;
            }
            var msg:String = StringUtil.substitute( "Type not found: '{0}'", typeName );
            if (moduleReference) {
               msg += "; referenced by: " + moduleReference;
            }
            Diagnostics.assert( false, msg );
            return null;
         }

         if (appDomain == ApplicationDomain.currentDomain) {
            // The main domain already threw an error and it will not
            // throw again if asked a second time for the same class.
            throw error;
         }
         try {
            classDefn = appDomain.getDefinition( typeName ) as Class;
         } catch (err:Error) {
            throw error;
         }
         return classDefn;
      }

      /**
       * Sets the value of a property (on the given object) that is tagged with the given
       * metatag. The given metatag can be of the form "a|b|...|z", in which case, each
       * individual tag is tried in order until a property with that tag is found.
       *
       * @return
       *    Returns true if such a property is found; false otherwise.
       */
      public static function setTaggedPropertyValue( propertyOwner:Object, metatag:String,
                                                     value:Object ):Boolean {
         var tags:Array/*String*/ = metatag.split( "|" );
         for each (var tag:String in tags) {
            var propFound:Boolean =
               setTaggedPropertyValue_SimpleTag( propertyOwner, tag, value );
            if (propFound) {
               return true;
            }
         }
         return false;
      }

      /**
       * Gets the value of a property (on the given object) that is tagged with the given
       * metatag. The given metatag can be of the form "a|b|...|z", in which case, each
       * individual tag is tried in order until a property with that tag is found.
       *
       * @return
       *    Returns the property value if such a property is found; returns null otherwise.
       */
      public static function getTaggedPropertyValue( propertyOwner:Object,
                                                     metatag:String ):Object {
         var tags:Array/*String*/ = metatag.split( "|" );
         for each (var tag:String in tags) {
            var propValInfo:PropertyValueInfo =
               getTaggedPropertyValue_SimpleTag( propertyOwner, tag );
            if (propValInfo.propertyExists) {
               return propValInfo.value;
            }
         }
         return null;
      }

      public static function getDeclaredPropertyType( propName:String,
                                                      propOwner:Object ):Class {
         var prop:PropertyReflectionInfo = getPropertyDescription( propOwner, propName );
         if (! prop) {
            return null;
         }
         var propTypeName:String = prop.typeName;
         var propType:Class = getDefinition( propTypeName, propOwner );
         return propType;
      }

      /**
       * While <code>getDeclaredPropertyType</code> return a <code>Class</code> object
       * this method returns the type name as <code>String</code>.
       * @see getDeclaredPropertyType
       */
      public static function getDeclaredPropertyTypeName(propName : String,
                                                         propOwner:Object):String {
         var prop:PropertyReflectionInfo = getPropertyDescription( propOwner, propName );
         if (! prop) {
            return null;
         }
         var propTypeName:String = prop.typeName;
         return propTypeName;
      }

      /**
       * Returns the access modifier of the property by the given name on the given object.
       * Possible values are "writeonly", "readonly" and "readwrite".
       *
       * @param propName
       *    Name of the property in question.
       * @param propProvider
       *    The object that has or provides the property.
       */
      public static function
         getPropertyAccessModifier( propName:String, propProvider:Object ):String {
         var prop:PropertyReflectionInfo = getPropertyDescription( propProvider, propName );
         if (prop == null) {
            return null;
         }
         return prop.access;
      }

      /**
       * Tests whether a property by the given name on the given object is tagged with the
       * given metatag. If an additional value arg is specified, it also checks whether this
       * value matches the parameter value specified in the tag.
       *
       * @param propName
       *    Name of the property in question.
       * @param propProvider
       *    The object that has or provides the property.
       * @param metatag
       *    The metatag name that the property is expected to be tagged with.
       * @param value
       *    An optional value for the parameter or argument of the tag. If this value is
       *    non-null, the tag param is supposed to be this value, in order for this method
       *    to return true.
       */
      public static function isPropertyTaggedWith( propName:String, propProvider:Object,
                                                   metatag:String, value:* = null ):Boolean {
         var prop:PropertyReflectionInfo =
            ReflectionManager.instance.getPropertyInfo(propProvider, propName);
         if (prop == null) {
            return false;
         }
         if (value == null) {
            return prop.hasTag(metatag);
         }
         var tagValues:Vector.<String> = prop.getTagArgValues(metatag);
         for each (var tagValue:String in tagValues) {
            if (tagValue == value) {
               return true;
            }
         }
         return false;
      }

      /**
       * Returns the type that is declared as the ArrayElementType for the property of the
       * given name on the given object.
       *
       * @param propName
       *    Name of the property in question.
       * @param propProvider
       *    The object that has or provides the property.
       */
      public static function
         getTaggedArrayElementType( propName:String, propProvider:Object ):Class {
         var prop:PropertyReflectionInfo =
            ReflectionManager.instance.getPropertyInfo(propProvider, propName);
         if (prop == null) {
            return null;
         }
         var values:Vector.<String> = prop.getTagArgValues("ArrayElementType");
         if (values.length == 0) {
            return null;
         }
         var elementTypeName:String = values[0];
         var elementType:Class = getDefinition( elementTypeName, propProvider );
         return elementType;
      }

      /**
       * Checks whether the first type is compatible with the second type.
       *
       * <p>Note that this function may not work right for the types that are not in the
       * current app domain.
       *
       * @param type1
       *  First class.
       * @param type2
       *  Second class.
       * @return
       *
       * @example
       *  isTypeCompatibleWith( TextArea, DisplayObject )  ----> returns true
       *  isTypeCompatibleWith( DisplayObject, TextArea )  ----> returns false
       */
      public static function isTypeCompatibleWith( type1:Class, type2:Class ):Boolean {
         if (type1 == type2 || type2 == Object) {
            return true;
         }
         var type1Name:String = getQualifiedClassName(type1);
         var type1IsSimple:Boolean = isSimpleType(type1Name);
         if (type1IsSimple) {
            return false;
         }
         var type2Name:String = getQualifiedClassName( type2 );
         var type2InType1Domain:Class = getDefinition( type2Name, type1 );
         if (type2InType1Domain != type2) {
            return false;
         }
         var classDesc:ClassReflectionInfo =
            ReflectionManager.instance.getClassInfo( type1 );
         if (classDesc.baseClassNames != null
            && classDesc.baseClassNames.indexOf(type2Name) >= 0) {
            return true;
         }
         if (classDesc.interfaceNames != null
            && classDesc.interfaceNames.indexOf(type2Name) >= 0) {
            return true;
         }
         return false;
      }

      public static function hasEvaluatableTokens( expr:String ):Boolean {
         var tokenStartIndex:int = expr.indexOf( "{" );
         if (tokenStartIndex < 0) {
            return false;
         }
         var tokenEndIndex:int = expr.indexOf( "}" );
         if (tokenEndIndex < tokenStartIndex) {
            return false;
         }
         return true;
      }

      [ArrayElementType("String")]
      public static function
         getPropertyTokensFromExpr( expr:String, propertyProvider:Object ):Array {
         var tokens:Array = MixedUtil.getEvaluatableTokens( expr );
         var propertyTokens:Array = MixedUtil.filter( tokens, isPropertyToken,
            propertyProvider );
         return propertyTokens;
      }

      /**
       * Resolves the tokens in the given metatag param value. A token is an expression of
       * the form {foo} and is assumed to represent a variable or constant with reference to
       * the given object.
       *
       * @see #getConstOrPropValue()
       */
      public static function resolveTagValue( tagVal:String,
                                              tagProvider:Object = null ):Object {
         if (tagVal == null) {
            return null;
         }
         var token:String = getFirstEvalToken( tagVal );
         if (token == null) {
            // there's no token to resolve, so return the value itself
            return tagVal;
         }
         var replacementToken:String = "{" + token + "}";
         if (replacementToken == tagVal) {
            // The *whole* tag value is an eval-token, so return whatever it evaluates to,
            // without any need to do further string formatting.
            var value:Object = getConstOrPropValue( token, tagProvider );
            return value;
         }
         var valueStr:String = tagVal;
         while (token != null) {
            var tokenVal:Object = getConstOrPropValue( token, tagProvider );
            var tokenValStr:String = tokenVal as String;
            if (! tokenValStr && tokenVal) {
               tokenValStr = tokenVal.toString();
            }
            valueStr = valueStr.replace( replacementToken, tokenValStr );
            token = getFirstEvalToken( valueStr );
            replacementToken = "{" + token + "}";
         }
         return valueStr;
      }

      /**
       * Indicates whether the given type name represents a flex type. For example, the
       * types in the mx and flash namespaces, the resource asset types, or the root Object
       * type. Notably, class names including "__" are considered to be Flex assets types.
       * This method performs a conservative check, i.e., a return value of false does not
       * necessarily mean that the type is not a flex framework type, but a return value of
       * true means that it definitely is.
       */
      public static function isFlexTypeName( typeName:String ):Boolean {
         // "__" filters out types like:
         // en_US$FlexWidgets_properties__embed_properties_______assets_swfs_...
         // and _ContainerApp_Styles__embed_css_assets_images_arrowRight_png_1338931880.

         // "mx_managers_SystemManager" filters out types like
         // _ContainerApp_mx_managers_SystemManager.
         if (typeName.indexOf("mx.") == 0 || typeName.indexOf("spark.") == 0 ||
            typeName.indexOf("flash.") == 0 ||
            typeName == "Object" || typeName.indexOf("__") >= 0 ||
            typeName.indexOf("mx_managers_SystemManager") >= 0) {
            return true;
         }
         return false;
      }

      /**
       * Same as isSparkTypeName except that this one takes an object as the arg,
       * which can be either an instance of or the Class object for the type in question.
       */
      public static function isSparkType( object:Object ):Boolean {
         var className:String = getQualifiedClassName( object );
         var isFlexType:Boolean = isSparkTypeName( className );
         return isFlexType;
      }

      /**
       * Indicates whether the given type name represents a spark type. A return value of
       * false does not necessarily mean that the type is not a spark framework type, but a
       * return value of true means that it definitely is.
       */
      public static function isSparkTypeName( typeName:String ):Boolean {
         if (typeName.indexOf("spark.") == 0) {
            return true;
         }
         return false;
      }

      /**
       * Detect if an object is a spark or mx skin.
       */
      public static function isFlexSkin(object:Object):Boolean {
         if (object is mx.skins.ProgrammaticSkin || object is spark.skins.SparkSkin) {
            return true;
         }
         return false;
      }

      /**
       * Same as isFlexTypeName except that this one takes an object as the arg,
       * which can be either an instance of or the Class object for the type in question.
       */
      public static function isFlexType( object:Object ):Boolean {
         var className:String = getQualifiedClassName( object );
         var isFlexType:Boolean = isFlexTypeName( className );
         return isFlexType;
      }

      /**
       * Returns the value of the named property on the given object if such a property
       * exists on the object, otherwise returns null. The caller is supposed to make sure
       * that the property is not <code>writeonly</code>.
       */
      public static function getPropertyValue( propertyProvider:Object,
                                               propertyName:String ):* {
         // Note that hasOwnProperty does not tell you that the property has read access as
         // well, but we leave that for the caller to take care of.
         if (propertyProvider == null || ! propertyProvider.hasOwnProperty( propertyName )) {
            return null;
         }
         var propertyValue:* = propertyProvider[propertyName];
         return propertyValue;
      }

      /**
       * Returns the value of the specified property on the given object. The property name
       * can be indefinitely nested (i.e., can be of the form x.y...z), or it can be a
       * simple (first order) property name.
       */
      public static function getNestedPropertyValue( propertyProvider:Object,
                                                     propertyName:String ):Object {
         if (propertyProvider == null) {
            return null;
         }
         var props:Array/*String*/ = propertyName.split( "." );
         var value:Object =
            getPropertyValue_SingleProvider_SplitPath( propertyProvider, props );
         return value;
      }

      /**
       * Sets the provided value as the specified property on the given object. The property
       * name can be indefinitely nested (i.e., can be of the form x.y...z), or it can be a
       * simple (first order) property name.
       */
      public static function setNestedPropertyValue( propertyProvider:Object,
                                                     propertyName:String,
                                                     newValue:Object ):void {
         if (propertyProvider == null) {
            return;
         }
         var props:Array/*String*/ = propertyName.split( "." );
         var target:Object = propertyProvider;
         for (var i:int = 0; i < props.length - 1; i++) {
            var prop:String = props[i];
            if (! target.hasOwnProperty( prop )) {
               var msg:String = StringUtil.substitute(
                  "Property {0} not found on {1}", prop, target );
               Diagnostics.assert( false, msg );
               return;
            }
            var propValue:Object = target[prop];
            target = propValue;
            if (target == null) {
               return;
            }
         }
         var nameOfLeafProp:String = props[props.length - 1];
         target[nameOfLeafProp] = newValue;
      }

      /**
       * @param propertyProvider
       *    The parameter is treated as the provider of the property (as opposed to being a
       *    collection of providers).
       */
      private static function getPropertyValue_SingleProvider_SplitPath(
         propertyProvider:Object, props:Array/*String*/, startIndex:int = 0 ):Object {
         var target:Object = propertyProvider;
         var value:Object;
         for (var i:int = startIndex; i < props.length; i++) {
            var prop:String = props[i];
            if (prop == "*") {
               var arrayVal:Array/*?*/ =
                  getPropertyValue_MultiProvider_SplitPath( target, props, i+1 );
               return arrayVal;
            }
            if (! target.hasOwnProperty( prop )) {
               var msg:String = StringUtil.substitute(
                  "Property {0} not found on {1}", prop, target );
               Diagnostics.assert( false, msg );
               return null;
            }
            value = target[prop];
            target = value;
            if (target == null) {
               return null;
            }
         }
         return value;
      }

      /**
       * @param propertyProvider
       *    The parameter is treated as a collection of providers sporting the said property.
       *    The collection is expected to be iterable through "for each".
       *
       * @return
       *    The collection of the values of the said property read off of the given
       *    property providers.
       */
      private static function getPropertyValue_MultiProvider_SplitPath(
         propProviders:Object, props:Array/*String*/, startIndex:int = 0 ):Array/*?*/ {
         var arrayVal:Array/*?*/ = [];
         for each (var propProvider:Object in propProviders) {
            var val:Object = getPropertyValue_SingleProvider_SplitPath( propProvider, props,
               startIndex );
            arrayVal.push( val );
         }
         return arrayVal;
      }

      private static function
         getTagParamValue( object:Object, tag:TagReflectionInfo, searchKey:String ):Object {
         var arg:TagArgReflectionInfo = tag.getArgInfoByKey(searchKey);
         if (arg == null) {
            return null;
         }
         var argValueToken:String = arg.value;
         var argValue:Object = resolveTagValue( argValueToken, object );
         return argValue;
      }

      /**
       * Only allows simple tags (e.g., "foo") as opposed to the public method that also
       * allows complex tags like "foo|bar".
       */
      private static function
         getSingleTaggedProperty_SimpleTag( object:Object, metatag:String ):TaggedPropertyInfo {
         var pi:TaggedPropertyInfo = getSingleTaggedVariable( object, metatag );
         if (pi == null) {
            pi = getSingleTaggedAccessor( object, metatag );
         }
         return pi;
      }

      [ArrayElementType("String")]
      /**
       * Finds the metatag definitions by the given name for the specified object/type and
       * returns the values of the specified parameter in all matching tag definitions. Base
       * classes of the object's concrete type are not considered.
       */
      private static function getUninheritedClassMetadata( object:Object,
                                                           metatag:String,
                                                           key:String ):Array/*String*/ {
         var values:Array = [];
         var tags:Vector.<TagReflectionInfo> =
            ReflectionManager.instance.getClassTagInfos(object, metatag);
         for each (var tag:TagReflectionInfo in tags) {
            var arg:TagArgReflectionInfo = tag.getArgInfoByKey(key);
            if (arg == null) {
               continue;
            }
            var tagValue:String = resolveTagValue(arg.value, object) as String;
            values.push(tagValue);
         }
         return values;
      }

      /**
       * Indicates whether the given object type is annotated with the given metadata tag.
       * Base classes of the object's concrete type are not considered.
       */
      public static function hasUninheritedClassMetadata( object:Object,
                                                          metatag:String ):Boolean {
         var tags:Vector.<TagReflectionInfo> =
            ReflectionManager.instance.getClassTagInfos(object, metatag);
         var hasTag:Boolean = tags.length != 0;
         return hasTag;
      }

      /**
       * Finds the class-level metatag definition for the given object and tag name, such
       * that the tag has the given searchValue for the parameter with the given searchKey.
       * It then returns the value of the tag paramter with the given valueKey. Base classes
       * of the object's concrete type are not considered.
       *
       * <p>If searchValue is null or undefined, looks for a tag that doesn't specify any
       * params with the given searchKey.
       */
      private static function
         getUninheritedFilteredClassTagValue( object:Object, metatag:String, searchKey:String,
                                              searchValue:String, valueKey:String ):String {
         var tags:Vector.<TagReflectionInfo> =
            ReflectionManager.instance.getClassTagInfos(object, metatag);
         var tagValue:String = getParamValueFromValueFilteredTag(
            object, tags, searchKey, searchValue, valueKey );
         return tagValue;
      }

      /**
       * Only allows simple tags (e.g., "foo") as opposed to the public method that also
       * allows complex tags like "foo|bar".
       */
      private static function
         setTaggedPropertyValue_SimpleTag( propertyOwner:Object, metatag:String,
                                           value:Object ):Boolean {
         var pi:TaggedPropertyInfo =
            ReflectionUtil.getSingleTaggedProperty( propertyOwner, metatag );
         if (pi == null || pi.access == ACCESS_READONLY) {
            return false;
         }
         propertyOwner[pi.name] = value;
         return true;
      }

      /**
       * Only allows simple tags (e.g., "foo") as opposed to the public method that also
       * allows complex tags like "foo|bar".
       */
      private static
      function getTaggedPropertyValue_SimpleTag( propertyOwner:Object,
                                                 tag:String ):PropertyValueInfo {
         var pi:TaggedPropertyInfo =
            ReflectionUtil.getSingleTaggedProperty( propertyOwner, tag );
         var propValInfo:PropertyValueInfo = new PropertyValueInfo();
         if (pi == null || pi.access == ACCESS_WRITEONLY) {
            return propValInfo;
         }
         propValInfo.propertyExists = true;
         propValInfo.value = propertyOwner[pi.name];
         return propValInfo;
      }

      private static function tagWithArgKeyAndValue( tags:Vector.<TagReflectionInfo>,
                                                     key:String, value:String,
                                                     tagProvider:* ):TagReflectionInfo {
         for each (var tag:TagReflectionInfo in tags) {
            for each (var arg:TagArgReflectionInfo in tag.argInfos) {
               var argVal:String = resolveTagValue( arg.value, tagProvider ) as String;
               if (arg.key == key && argVal == value) {
                  return tag;
               }
            }
         }
         return null;
      }

      private static function getFirstEvalToken( expr:String, startIndex:int = 0 ):String {
         var tokenStartIndex:int = expr.indexOf( "{", startIndex );
         if (tokenStartIndex < 0) {
            // no token to resolve
            return null;
         }
         var tokenEndIndex:int = expr.indexOf( "}", startIndex );
         if (tokenEndIndex < tokenStartIndex) {
            Diagnostics.assert( false, "Bad eval-token format: " + expr );
            return null;
         }
         var token:String = expr.substring( tokenStartIndex + 1, tokenEndIndex );
         return token;
      }

      /**
       * Resolves the given name assuming it's either a public property of the given
       * reference object, or the id of a constant where the id is either relative to the
       * reference or is fully qualified.
       *
       * @see #getClassConstValue()
       */
      public static function getConstOrPropValue( name:String, reference:Object ):Object {
         var val:Object;
         if (isPropertyToken( name, reference )) {
            val = getNestedPropertyValue( reference, name );
            return val;
         }
         // The token is not a property on the provider object, so we assume the value
         // is a const defined in some class.
         val = getClassConstValue( name, reference );
         return val;
      }

      /**
       * Looks up the value of the constant of static var identified by the given id, with
       * reference to the given object. The id is either the var name for the const if found
       * in the reference object, or a fully qualified name that includes the full name of
       * the class that defines the const. The package or namespace of the class could be
       * aliased using the [Using] metatag on the class definition of the reference object.
       *
       * <p>Examples of constId: "FOO", "some.namespace.SomeClass.FOO", "ns:SomeClass.FOO"
       * <p>where "ns" is defined in a class level metatag on the class of the reference
       * object, for example:
       * <p>   [Using( name="some.namespace", alias="ns" )]
       */
      public static function getClassConstValue( constId:String,
                                                 reference:Object ):Object {
         var constDefiningClass:Object;
         var dotIndex:int = constId.lastIndexOf( "." );
         var typeName:String;
         var propNameStartIndex:int = (dotIndex < 0)? 0 : (dotIndex + 1);
         var propName:String =
            constId.substring( propNameStartIndex, constId.length );
         if (dotIndex == -1 && reference != null) {
            constDefiningClass = Object( reference ).constructor;
         } else {
            var normalizedConstId:String = MixedUtil.normalizeClassName( constId );
            var colonIndex:int = normalizedConstId.lastIndexOf( ":" );
            if (colonIndex == -1) {
               typeName = constId.substring( 0, dotIndex );
            } else {
               var alias:String = normalizedConstId.substring( 0, colonIndex );
               var className:String = constId.substring( colonIndex + 1, dotIndex );
               var packageName:String = getUninheritedFilteredClassTagValue(
                  reference, "Using", "alias", alias, "name" );
               typeName = packageName + "." + className;
            }
            constDefiningClass = getDefinition( typeName, reference );
         }
         if (! constDefiningClass.hasOwnProperty( propName )) {
            typeName = getQualifiedClassName( constDefiningClass );
            Diagnostics.assert( false,
               typeName + " doesn't have a public property by the name: " + propName );
         }
         var val:Object = getNestedPropertyValue( constDefiningClass, propName );
         return val;
      }

      /**
       * Returns true if the given token represents a property of the given object (as
       * opposed to some constant referenced through a class name).
       */
      private static function isPropertyToken( token:String, propertyProvider:* ):Boolean {
         if (propertyProvider == null || token.indexOf( "::" ) >= 0) {
            // Either there's no property provider instance, or the token clearly contains a
            // type name (which means it must be referring to a constant).
            return false;
         }
         var propName:String = MixedUtil.getFirstWord( token, "." );
         if (! propertyProvider.hasOwnProperty( propName )) {
            return false;
         }
         return true;
      }

      /**
       * Checks if an object is of the supplied type.
       *
       * @param object
       *    Object whose type is to be checked.
       * @param typeName
       *    Name of the type against which to check object.
       * @return
       *    True if object is of type represented by typeName or if object extends type
       *    represented by typeName. Will all return true if object is null or the typeName
       *    is null/empty.
       *
       */
      public static function isOfType( object:Object,
                                          typeName:String ):Boolean {
         typeName = StringUtil.trim(typeName);
         // If object is null or typename is not known assume it is assignable
         if (object == null || StringUtils.isNullorEmpty( typeName )) {
            return true;
         }
         var type:Class = getDefinition( typeName, object );
         return object is type;
      }

      /**
       * Returns the method in the given owner, having the given name.
       * Note that only tagged methods are considered.
       */
      private static function getMethodDescription( methodOwner:Object,
                                                    methodName:String ):MethodReflectionInfo {
         var methodInfo:MethodReflectionInfo =
            ReflectionManager.instance.getTaggedMethodInfo(methodOwner, methodName);
         return methodInfo;
      }

      private static function getPropertyDescription( propOwner:Object,
                                                      propName:String ):PropertyReflectionInfo {
         var prop:PropertyReflectionInfo =
            ReflectionManager.instance.getPropertyInfo(propOwner, propName);
         return prop;
      }

      /**
       * Utility method which returns property information found in the class description
       * for this object.
       */
      private static function getPropertyDescriptions( object:Object,
                                                       accessorMode:String = ACCESS_ANY,
                                                       sortTag:String = null) : Vector.<PropertyReflectionInfo> {
         var props:Vector.<PropertyReflectionInfo> =
            ReflectionManager.instance.getPropertyInfos(object, accessorMode);
         // The call below is efficient and if no processing order is specified
         // it will do nothing.
         props = sortByProcessingOrder(props, sortTag);
         return props;
      }

      /**
       * Sort the provided list of property metadata info according to the
       * [ProcessingOrder(tag="sortTag", order="..."] tag data.
       *
       * @param props
       *    The list of properties (variables and accessors) metadata.
       *
       * @param sortTag
       *    The tag by which to oder the properties information. Some properties
       *    may have multiple [ProcessingOrder] tags, so this one specifies by which
       *    one exactly to sort them.
       *
       * @return If the <code>sortTag</code> is not empty (or null) a new list is
       *    constructed where the properties information is sorted accordingly.
       *    Otherwise the original list is returned (for efficiency reasonse we avoid
       *    constructring a new list in this case).
       *
       * <p/> e.g.
       * <listing version="3.0">
       * class UserData {
       *    [ProcessingOrder(tag="Persistable", order="-1"]
       *    public var name:String;
       *
       *    [ProcessingOrder(tag="Persistable", order="-2"]
       *    public var surname:String;
       *
       *    [ProcessingOrder(tag="Persistable", order=1)]
       *    [ProcessingOrder(tag="Injectable", order="-10")]
       *    public var phone:String;
       *
       *    // This one has no processing tag, so it is treated as having a
       *    // default processing order of 0 for any tag.
       *    public var address:String;
       * }
       * </listing>
       *
       * In the example above, when the service which processes [Persistable] tags
       * will use the ordering specified, by first processing "surname"
       * (order = -2), then "name" (order = -1), then "address" (order = 0) and then
       * "phone" (order = 1).
       *
       * <p/> Since this service does not care about [Injectable] tags, it will
       * ignore the [ProcessingOrder(tag="Injectable", order="-10")] metadata.
       */
      private static function sortByProcessingOrder(props:Vector.<PropertyReflectionInfo>,
                                                    sortTag:String):Vector.<PropertyReflectionInfo> {
         // An early exit condition. Saves unneded processing.
         if (StringUtils.isNullorEmpty(sortTag)) {
            return props;
         }

         var ordered:ArrayCollection = new ArrayCollection();
         var retVal:Vector.<PropertyReflectionInfo> = new Vector.<PropertyReflectionInfo>();

         // We need just a single pass over the properties to sort them appropriately
         for each (var prop:PropertyReflectionInfo in props) {
            // Default sorting order is 0.
            var sortTagOrder:int = 0;
            var sortTagFound:Boolean = false;

            // Check for [ProcessingOrder(tag="...", order="...")] metadata.
            var orderTags:Vector.<TagReflectionInfo> =
               prop.getTagInfosByName(PROCESSING_ORDER_TAG);
            // For each [ProcessingOrder(tag="...", order="...")]
            for each(var orderTag:TagReflectionInfo in orderTags) {
               var order:int = 0;
               for each (var arg:TagArgReflectionInfo in orderTag.argInfos) {
                  // [...(tag=sortTag, ...)]
                  if (arg.key == TAG_KEY && arg.value == sortTag) {
                     sortTagFound = true;
                  }
                  // [...(..., order=...)]
                  if (arg.key == ORDER_KEY) {
                     order = int(arg.value);
                  }
               }

               // The first tag which matches our "sortTag" is enough. No
               // need to process other [ProcessingOrder] for this property.
               if (sortTagFound) {
                  sortTagOrder = order;
                  break;
               }
            }

            // Figure out exactly where to put this property considering
            // the processing order.
            for (var i:int = 0; i < ordered.length; ++i) {
               if (ordered[i] > sortTagOrder) {
                  break;
               }
            }

            // Keep the order
            ordered.addItemAt(sortTagOrder, i);

            // Keep the property.
            retVal.splice(i, 0, prop);
         }

         return retVal;
      }


      private static function getTaggedPropertyDescriptions( object:Object,
                                                             metatag:String, sortTag:String = null):Vector.<PropertyReflectionInfo> {
         var result:Vector.<PropertyReflectionInfo> =
            ReflectionManager.instance.getTaggedPropertyInfos(object, metatag);
         result = sortByProcessingOrder(result, sortTag);
         return result;
      }

      private static function getTaggedVariableDescriptions( object:Object,
                                                             metatag:String ):Vector.<PropertyReflectionInfo> {
         var props:Vector.<PropertyReflectionInfo> =
            getTaggedPropertyDescriptions( object, metatag );
         var variables:Vector.<PropertyReflectionInfo> =
            new Vector.<PropertyReflectionInfo>();
         for each (var pi:PropertyReflectionInfo in props) {
            if (pi.isVariable) {
               variables.push( pi );
            }
         }
         return variables;
      }

      private static function getTaggedAccessorDescriptions( object:Object,
                                                             metatag:String ):Vector.<PropertyReflectionInfo> {
         var props:Vector.<PropertyReflectionInfo> =
            getTaggedPropertyDescriptions( object, metatag );
         var accessors:Vector.<PropertyReflectionInfo> =
            new Vector.<PropertyReflectionInfo>();
         for each (var pi:PropertyReflectionInfo in props) {
            if (!pi.isVariable) {
               accessors.push( pi );
            }
         }
         return accessors;
      }

      private static function
         getTaggedMethodDescriptions( object:Object, metatag:String,
                                      value:String = null):Vector.<MethodReflectionInfo> {
         var taggedMethods:Vector.<MethodReflectionInfo> =
            ReflectionManager.instance.getTaggedMethodInfos(object, metatag);
         if (value == null) {
            return taggedMethods;
         }
         var methods:Vector.<MethodReflectionInfo> = new Vector.<MethodReflectionInfo>();
         for each (var method:MethodReflectionInfo in taggedMethods) {
            var tagVals:Vector.<String> = method.getTagArgValues(metatag);
            if (tagVals.length == 0) {
               continue;
            }
            var tagValue:String = tagVals[0];
            tagValue = resolveTagValue( tagValue, object ) as String;
            if (tagValue != value) {
               continue;
            }
            methods.push( method );
         }
         return methods;
      }

      /**
       * Return the unqualified class name.
       */
      public static function getSimpleClassName(value:*):String {
         var name:String = getQualifiedClassName(value);
         var pos:int = name.search("::");
         if (pos > -1) {
            // Cut off the package part.
            return name.substr(pos+2);
         } else {
            // Some global name.
            return name;
         }
      }

      private static function newPropertyInfo( prop:PropertyReflectionInfo,
                                               moduleReference:* = null ):TaggedPropertyInfo {
         var pi:TaggedPropertyInfo = new TaggedPropertyInfo();
         pi.name = prop.name;
         pi.propertyTypeName = prop.typeName;
         pi.access = prop.access;
         pi.propertyType = getDefinition( pi.propertyTypeName, moduleReference );
         return pi;
      }

      private static function newMethodInfo( method:MethodReflectionInfo,
                                             moduleReference:* ):TaggedMethodInfo {
         var mi:TaggedMethodInfo = new TaggedMethodInfo();
         mi.name = method.name;
         mi.declaringTypeName = method.declaringTypeName;
         mi.moduleReference = moduleReference;
         return mi;
      }

   } // class

}

/**
 * Holds information about a property and its value.
 */
class PropertyValueInfo
{
   /** Indicates whether the property in question exists on some given object or not. */
   public var propertyExists:Boolean = false;

   /**
    * Value of the property in question on some given object (assuming it has the
    * property).
    */
   public var value:Object = null;
}